Skip to content

perf: Lazily load in BitpackedCodec - #56

Merged
stuhood merged 1 commit into
mainfrom
stuhood.avoid-owned-bytes
Jul 23, 2025
Merged

perf: Lazily load in BitpackedCodec#56
stuhood merged 1 commit into
mainfrom
stuhood.avoid-owned-bytes

Conversation

@stuhood

@stuhood stuhood commented Jul 23, 2025

Copy link
Copy Markdown
Collaborator

We would like to be able to lazily load BitpackedCodec columns (similar to what 020bdff did for BlockwiseLinearCodec), because in the context of pg_search, immediately constructing OwnedBytes means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from BitUnpacker, and then lazily load each block when it is requested. Only the get_val function uses the cache: get_row_ids_for_value_range does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use get_vals, for which the default implementation is to just call get_val in a loop.

@stuhood
stuhood force-pushed the stuhood.avoid-owned-bytes branch from 780b33b to 60d9626 Compare July 23, 2025 16:18

@eeeebbbbrrrr eeeebbbbrrrr left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're comfortable with the test coverage here, especially edge cases around block boundaries, then so am I.

Good work. Super excited to get this available in pg_search.

@eeeebbbbrrrr

Copy link
Copy Markdown

Unfortunately there are also some 13-19% slowdowns on aggregates: it's possible that porting get_row_ids_for_value_range to use the cache would win that back: let me know if you think that we should do it here.

Lets do whatever might be necessary to claw back some of this in a separate PR after this one is merged.

@stuhood

stuhood commented Jul 23, 2025

Copy link
Copy Markdown
Collaborator Author

If you're comfortable with the test coverage here, especially edge cases around block boundaries, then so am I.

I am: Tantivy's own test suite provides a lot of coverage (I know, because I fought with it quite a bit here).

Unfortunately there are also some 13-19% slowdowns on aggregates: it's possible that porting get_row_ids_for_value_range to use the cache would win that back: let me know if you think that we should do it here.

Lets do whatever might be necessary to claw back some of this in a separate PR after this one is merged.

I looked into this more, and apparently aggregates do not use a batch decoding method like get_row_ids_for_value_range: they just use get_vals, which defaults to an (unrolled) loop over get_val.

Basically: the problem is more fundamental. We have work to do.

@stuhood
stuhood merged commit 55bd31e into main Jul 23, 2025
5 checks passed
@stuhood
stuhood deleted the stuhood.avoid-owned-bytes branch July 23, 2025 19:46
stuhood added a commit to paradedb/paradedb that referenced this pull request Jul 23, 2025
## What

Incorporates paradedb/tantivy#56.

## Why

As mentioned there:
> We would like to be able to lazily load `BitpackedCodec` columns
(similar to what
paradedb/tantivy@020bdff
did for `BlockwiseLinearCodec`), because in the context of `pg_search`,
immediately constructing `OwnedBytes` means copying the entire content
of the column into memory.

## Tests

There are a few 2x speedups in the benchmark suite, as well as a 1.8x
speedup on a representative customer query.

Unfortunately there are also some 13-19% slowdowns on aggregates with
`solve_mvcc=false`: it looks like that is because aggregates use
`get_vals`, for which the default implementation is to just call
`get_val` in a loop. After discussion, we think that getting back that
performance might require wider API changes to make batching more
inherent.
mdashti pushed a commit that referenced this pull request Oct 21, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
mdashti pushed a commit that referenced this pull request Oct 22, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
mdashti pushed a commit that referenced this pull request Oct 22, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
mdashti pushed a commit that referenced this pull request Dec 3, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
mdashti pushed a commit that referenced this pull request Dec 3, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Dec 3, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Dec 10, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Dec 10, 2025
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Jan 13, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Jan 13, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Jan 13, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Jan 14, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
stuhood added a commit that referenced this pull request Jan 29, 2026
…itpacker (#97)

## What

Fixes a panic in `FileSlice::combine_ranges` that occurs when querying segments with high document counts (e.g., >67M docs with 64-bit values). It was introduced in #56.

The issue was caused by `u32` integer overflow during `doc_id * num_bits` calculations. This resulted in wrapped-around offsets and invalid ranges where `start > end`, triggering an assertion failure in the file slicing logic.

## How

Cast from `u32` to `usize` before multiplication in a variety of locations.

Additionally, add an assert in `block_oblivious_range` to provide a clear failure if an out-of-bounds start is requested, preventing invalid ranges from reaching `FileSlice`.

## Tests

Adds a test that fails with overflow before the fix.
rebasedming pushed a commit that referenced this pull request Jan 30, 2026
…itpacker (#97)

## What

Fixes a panic in `FileSlice::combine_ranges` that occurs when querying segments with high document counts (e.g., >67M docs with 64-bit values). It was introduced in #56.

The issue was caused by `u32` integer overflow during `doc_id * num_bits` calculations. This resulted in wrapped-around offsets and invalid ranges where `start > end`, triggering an assertion failure in the file slicing logic.

## How

Cast from `u32` to `usize` before multiplication in a variety of locations.

Additionally, add an assert in `block_oblivious_range` to provide a clear failure if an out-of-bounds start is requested, preventing invalid ranges from reaching `FileSlice`.

## Tests

Adds a test that fails with overflow before the fix.
rebasedming pushed a commit that referenced this pull request Feb 10, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
rebasedming pushed a commit that referenced this pull request Feb 10, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.
rebasedming pushed a commit that referenced this pull request Jun 22, 2026
We would like to be able to lazily load `BitpackedCodec` columns (similar to what 020bdff did for `BlockwiseLinearCodec`), because in the context of `pg_search`, immediately constructing `OwnedBytes` means copying the entire content of the column into memory.

To do so, we expose some (slightly overlapped) block boundaries from `BitUnpacker`, and then lazily load each block when it is requested. Only the `get_val` function uses the cache: `get_row_ids_for_value_range` does not (yet), because it would be necessary to partition the row ids by block, and most of the time consumers using it are already loading reasonably large ranges anyway.

See paradedb/paradedb#2894 for usage. There are a few 2x speedups in the benchmark suite, as well as a 1.8x speedup on a representative customer query. Unfortunately there are also some 13-19% slowdowns on aggregates: it looks like that is because aggregates use `get_vals`, for which the default implementation is to just call `get_val` in a loop.

(cherry picked from commit 57d2ef8)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants